#include "EPluginLoader.h"
#include "DefineCommon.h"
#include "EPlugin.h"
#include <cstddef>
#include <string>
#ifdef WIN32
#include <Windows.h>
#include <strsafe.h>
#else
#include <dlfcn.h>
#include <unistd.h>
#endif

#ifdef WIN32
std::string EPluginLoader_DwordToString(DWORD val)
{
    std::string cur_str = std::to_string(long long(val));
    return cur_str;
}

std::string EPluginLoader_ConvertLPCTSTRToString(LPCTSTR lpctstr) 
{
   int length = lstrlen(lpctstr);
   int size = WideCharToMultiByte(CP_UTF8, 0, (LPCWCH)lpctstr, length, nullptr, 0, nullptr, nullptr);
   if (size == 0) {
       // 转换失败处理
       return "";
   }

   std::string str(size, '\0');
   WideCharToMultiByte(CP_UTF8, 0, (LPCWCH)lpctstr, length + 1, &str[0], size, nullptr, nullptr);

   return str;
}

std::string EPluginLoader_currentPath()
{
    std::string path;
    char buffer[MAX_PATH];
    if(GetModuleFileName(NULL, buffer, MAX_PATH))
    {
        path = buffer;
        size_t tempIndex = path.find_last_of("\\");
        path = path.substr(0, tempIndex);
    }
    return path;
}

std::string EPluginLoader_getPluginPath(const std::string& exeDir, const std::string& pluginName)
{
    std::string pathPlugin = exeDir 
            +"\\plugin\\"
            +pluginName
            +".dll";
    return pathPlugin;
}

bool EPluginLoader_loadLibrary(const char* libFileName, EPlugin::HandleDll& outHandle, EPlugin::Fun_Address& outFun)
{
    outHandle = nullptr;
    outFun = nullptr;
    bool result = false;

    //ELOG_INFO("try to load library: %s", libFileName);
    //study resource: https://learn.microsoft.com/zh-cn/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibrarya?redirectedfrom=MSDN
    //stduy resource: https://learn.microsoft.com/zh-cn/windows/win32/debug/retrieving-the-last-error-code
    //for Debug exe, only Debug dll can be loaded. It is same with release version.
    outHandle = LoadLibraryA(libFileName);
    // funExport = (Fun_EPluginExportFunction)GetProcAddress((HMODULE)handle, "EPluginExportFunction");
    
    LPVOID lpMsgBuf;
    LPVOID lpDisplayBuf;
    DWORD dw = GetLastError();
    std::string errorStr = EPluginLoader_DwordToString(dw);

    if (errorStr == "0")
    {
        ELOG_INFO("Load library %s successfully", libFileName);
        outFun = GetProcAddress((HMODULE)outHandle, "EPluginExportFunction");

        if(outFun == nullptr)
        {
            ELOG_ERROR("Failed to find function \"EPluginExportFunction\"");
        }
        else
        {
            // funExport();
            // ELOG_DEBUG("Library export function is found successfully");
            result = true;
        }
    }
    else
    {
        //print some error string
        ELOG_ERROR("Load library %s failed, error str =: %s", libFileName, errorStr.data());
        FormatMessage(
            FORMAT_MESSAGE_ALLOCATE_BUFFER |
            FORMAT_MESSAGE_FROM_SYSTEM |
            FORMAT_MESSAGE_IGNORE_INSERTS,
            NULL,
            dw,
            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
            (LPTSTR)&lpMsgBuf,
            0, NULL);

        // Display the error message and exit the process

        lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT, (lstrlen((LPCTSTR)lpMsgBuf) + 40) * sizeof(TCHAR));
        StringCchPrintf(
            (LPTSTR)lpDisplayBuf,
            LocalSize(lpDisplayBuf) / sizeof(TCHAR),
            TEXT("failed with error %d: %s"),
            dw, lpMsgBuf
        );
        // MessageBox(NULL, (LPCTSTR)lpDisplayBuf, TEXT("Error"), MB_OK);
        // TODO: I do not want to show a MessageBox to show detail errorStr
        // But, it is difficult to convert LPTSTR.
        std::string errorStrDetail = EPluginLoader_ConvertLPCTSTRToString((LPCTSTR)lpDisplayBuf);
        ELOG_INFO("Detail Error String=%s", lpDisplayBuf);

        LocalFree(lpMsgBuf);
        LocalFree(lpDisplayBuf);
    }

    //TODO: add linux load code.
            // libHandle_ = dlopen(name_.c_str(), RTLD_LAZY);
            // if (!libHandle_)
                // saveErrorMsg();
            // return libHandle_ != NULL;

    return result;
}

#else
std::string EPluginLoader_currentPath()
{
    std::string result;
    char buffer[1024];
    size_t length = readlink("/proc/self/exe", buffer, sizeof(buffer)-1);
    if(length > 0)
    {
        buffer[length] = '\0';
        result = buffer;
        size_t lastSlashIndex = result.rfind('/');
        if(lastSlashIndex != std::string::npos)
        {
            result = result.substr(0, lastSlashIndex);
        }
    }
    return result;
}

std::string EPluginLoader_getPluginPath(const std::string& exeDir, const std::string& pluginName)
{
    std::string pathPlugin = exeDir 
            +"/plugin/lib"
            +pluginName
            +".so";
    return pathPlugin;
}

bool EPluginLoader_loadLibrary(const char* libFileName, EPlugin::HandleDll& outHandle, EPlugin::Fun_Address& outFun)
{
    outHandle = nullptr;
    outFun = nullptr;
    bool result = false;

    //ELOG_INFO("try to load library: %s", libFileName);
    

    outHandle = dlopen(libFileName, RTLD_LAZY);
    char* strError = dlerror();

    // dlerror() must be called twice
    // in order to avoid memory leaks
    if(strError != nullptr)
    {
        ELOG_ERROR("Load library %s failed, error str =: %s", libFileName, strError);
    }
    else 
    {
        ELOG_INFO("Load library %s successfully", libFileName);
    }
    strError = dlerror();

    if (outHandle != nullptr)
    {
        outFun = dlsym(outHandle, "EPluginExportFunction");
        char* strError = dlerror();

        if(strError != nullptr)
        {
            ELOG_ERROR("Load library %s failed, error str =: %s", libFileName, strError);
        }
        else 
        {
            result = true;
        }
        strError = dlerror();
    }
    else 
    {
        ELOG_ERROR("Load library %s failed, dlopen() return nullptr", libFileName );
    }


    return result;
}

#endif

struct EPluginLoaderData
{
    EList<EPluginPtr> m_pluginList;
};

EPluginLoader* EPluginLoader::m_instance = nullptr;

EPluginLoader::EPluginLoader()
{
    m_data = new EPluginLoaderData;
}

EPluginLoader::~EPluginLoader()
{
    delete m_data;
}
    
EPluginLoader* EPluginLoader::instance()
{
    if(m_instance == nullptr)
    {
        m_instance = new EPluginLoader();
    }
    return m_instance;
}

void EPluginLoader::loadPlugin(const char* pluginName)
{
    std::string path = EPluginLoader_currentPath();
    std::string pathPlugin = EPluginLoader_getPluginPath(path, pluginName);
    // ELOG_DEBUG("%s", pathPlugin.data());


    EPlugin::HandleDll handle = nullptr;
    EPlugin::Fun_Address fun = nullptr;

    if(pathPlugin.size() != 0 && 
            this->loadLibrary(pathPlugin.data()
                , handle
                , fun))
    {
        EPlugin::Fun_EPluginExportFunction funPtr = (EPlugin::Fun_EPluginExportFunction)(fun);
        EPluginPtr pp = funPtr();
        if(pp != nullptr)
        {
            pp->registThis();
            this->m_data->m_pluginList.push_back(pp);
        }
    }

}


bool EPluginLoader::loadLibrary(const char* libFileName, EPlugin::HandleDll& outHandle, EPlugin::Fun_Address& outFun)
{
    return EPluginLoader_loadLibrary( libFileName, outHandle, outFun);
}

size_t EPluginLoader::getPluginSize()
{
    return m_data->m_pluginList.size();
}

EPluginPtr EPluginLoader::getPlugin(size_t index)
{
    if(index >=0 && index < m_data->m_pluginList.size())
        return m_data->m_pluginList[index];
    return nullptr;
}
